using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Mail;
using System.Net.Mime;
using System.Text;
using Sustainalytics.Entities;
using Sustainalytics.Synchronization.Validation.Internals;
using Sustainalytics.Synchronization.Validation.ValidationRules.Common;
using Sustainalytics.Synchronization.Validation.Validators;

namespace Sustainalytics.Synchronization.Validation.Log
{
    public class MailClient : ILogClient
    {
        private readonly RulesList rulesList;
        private readonly string deploymentLocation;

        private readonly IMessageFormatter formatter;
        private readonly EmailSettings settings;

        private readonly SmtpClient smtpClient;

        public MailClient(
            EmailSettings settings,
            SmtpClient smtpClient,
            string deploymentLocation,
            IMessageFormatter formatter,
            RulesList rulesList)
        {
            this.smtpClient = smtpClient;
            this.formatter = formatter;
            this.rulesList = rulesList;
            this.deploymentLocation = string.Equals(
                deploymentLocation,
                "development",
                StringComparison.InvariantCultureIgnoreCase)
                                          ? Environment.MachineName.ToUpperInvariant()
                                          : deploymentLocation;
            this.settings = settings;
        }

        public void WriteAsync(IEnumerable<ValidationFailure> messages)
        {
            var subjStart = "Validation Results";

            var subject = string.Format("{0} {1}", subjStart, deploymentLocation);

            var sb = BuildAttachment(messages);

            var mailContent = BuildOverview(messages);

            SendEmail(new[] { settings.To }, subject, mailContent.ToString(), "ValidationResults.html", Encoding.UTF8.GetBytes(sb.ToString()));
        }

        private static void AddRecipients(string[] recipients, MailMessage mail)
        {
            if (recipients != null && recipients.Any())
            {
                mail.To.Clear();

                for (var i = 0; i < recipients.Count(); i++)
                {
                    mail.To.Add(new MailAddress(recipients.ElementAt(i)));
                }
            }
        }

        private static void BuildContent(string attachmentName, byte[] attachmentContent, MailMessage mail)
        {
            var contentType = new ContentType { Name = attachmentName, MediaType = MediaTypeNames.Application.Octet };

            // compress attachments larger than the set archiving threshold
            var archivingFolder = Directory.CreateDirectory("ArchivingDir");

            File.WriteAllBytes(Path.Combine(archivingFolder.Name, contentType.Name), attachmentContent);
            contentType.Name += ".zip";

            ZipFile.CreateFromDirectory(archivingFolder.Name, contentType.Name);

            // GZipStream/DeflateStream does not provide a way to include a file name in the header of the zipped stream
            attachmentContent = File.ReadAllBytes(contentType.Name);
            archivingFolder.Delete(true);
            File.Delete(contentType.Name);

            mail.Attachments.Add(new Attachment(new MemoryStream(attachmentContent), contentType));
        }

        private static string GetCellColor(BusinessResearchProduct research, RuleMetadata rule)
        {
            var color = rule.Severity == Severity.Red ? "#FCC2B2" : "#FFE9B2";
            return rule.BusinessResearchProduct == research ? color : "#fff";
        }

        private static string GetCount(GroupedRulesMetadata info, RuleMetadata rule, BusinessResearchProduct research)
        {
            var count = info.ResearchProduct == research && info.RuleName == rule.Code
                            ? info.BrokenRulesCount.ToString()
                            : string.Empty;
            return count;
        }

        private static List<GroupedRules> GetGroupedResults(IEnumerable<ValidationFailure> messages)
        {
            var groupedItems = from i in messages
                               group i by i.Severity
                                   into newGroup1
                               from newGroup2 in
                               from y in newGroup1
                               group y by y.Code
                                       into g
                               select new
                               {
                                   g.Key,
                                   Count = g.Count(),
                                   g.FirstOrDefault().ResearchProduct
                               }
                               group newGroup2 by newGroup1.Key
                                       into result
                               select new GroupedRules
                               {
                                   Severity = result.Key,

                                   RulesAggregation = result.Select(x => new GroupedRulesMetadata { RuleName = x.Key, BrokenRulesCount = x.Count, ResearchProduct = x.ResearchProduct })
                               };
            return groupedItems.ToList();
        }

        private StringBuilder BuildAttachment(IEnumerable<ValidationFailure> messages)
        {
            var sb = new StringBuilder();

            sb.AppendLine("<!DOCTYPE html>");
            sb.AppendLine("<html>");
            sb.AppendLine("<head>");
            sb.AppendLine("<title>End on gate quality control log</title>");
            sb.AppendLine("</head>");
            sb.AppendLine("<body>");

            formatter.LoadAdditionalDataAsync(messages.Select(x => x.Key).Distinct()).Wait();

            foreach (var data in messages)
            {
                var triggers = (data.Triggers != null && data.Triggers.Any())? $" - Triggers: {string.Join("#", data.Triggers)}" : string.Empty;
                sb.Append($"<p>{formatter.Format(data)}{triggers}</p>{Environment.NewLine}");
            }

            sb.AppendLine("</body>");
            sb.AppendLine("</html>");
            return sb;
        }

        private StringBuilder BuildOverview(IEnumerable<ValidationFailure> messages)
        {
            var result = new StringBuilder();
            result.AppendLine("<!DOCTYPE html>");
            result.AppendLine("<html>");
            result.AppendLine("<head>");
            result.AppendLine("<title>End on gate quality control log</title>");
            result.AppendLine("</head>");
            result.AppendLine("<body>");

            var aggregatedResults = GetGroupedResults(messages);

            foreach (Severity s in Enum.GetValues(typeof(Severity)))
            {
                var severity = s;
                result.AppendLine("<table>");

                result.AppendLine("<tr>");
                result.Append("<th>&nbsp;</th>");
                foreach (var rule in rulesList.GetRulesBySeverity(severity))
                {
                    result.AppendFormat("<th>{0}</th>", rule.Code);
                }

                result.AppendLine("</tr>");

                foreach (BusinessResearchProduct research in Enum.GetValues(typeof(BusinessResearchProduct)))
                {
                    result.Append("<tr>");
                    result.AppendFormat("<td>{0}</td>", research.GetDisplayName());

                    var items = from rule in rulesList.GetRulesBySeverity(severity)
                                join info in
                                    aggregatedResults.Where(x => x.Severity == severity)
                                    .SelectMany(x => x.RulesAggregation) on
                                    new { BP = rule.BusinessResearchProduct, Code = rule.Code } equals
                                    new { BP = info.ResearchProduct, Code = info.RuleName } into gj
                                from temp in gj.DefaultIfEmpty()
                                select
                                    string.Format(
                                        "<td bgcolor={0}>{1}</td>",
                                        GetCellColor(research, rule),
                                        GetCount(temp, rule, research));

                    foreach (var row in items)
                    {
                        result.AppendLine(row);
                    }

                    result.AppendLine("</tr>");
                }

                result.AppendLine("</table>");
            }

            result.AppendLine("<p>&nbsp;</p>");

            result.AppendLine("<table style=\"border-collapse: collapse;\">");

            foreach (BusinessResearchProduct research in Enum.GetValues(typeof(BusinessResearchProduct)))
            {
                var rulesCount = rulesList.GetRulesByBusinessResearchProduct(research).Count;
                var researchRendered = false;

                foreach (var rule in rulesList.GetRulesByBusinessResearchProduct(research))
                {
                    result.AppendLine("<tr>");

                    if (!researchRendered)
                    {
                        researchRendered = true;
                        result.AppendFormat(
                            "<td rowspan=\"{0}\" style=\"border:1px solid black\">{1}</td><td bgcolor={2} style=\"border:1px solid black\">{3}</td><td style=\"border:1px solid black\">{4}</td>",
                            rulesCount,
                            research.GetDisplayName(),
                            rule.Severity == Severity.Red ? "#FCC2B2" : "#FFE9B2",
                            rule.Code,
                            rule.InformationalMessage);
                    }
                    else
                    {
                        result.AppendFormat(
                            "<td bgcolor={0} style=\"border:1px solid black\">{1}</td><td style=\"border:1px solid black\">{2}</td>",
                            rule.Severity == Severity.Red ? "#FCC2B2" : "#FFE9B2",
                            rule.Code,
                            rule.InformationalMessage);
                    }

                    result.AppendLine("</tr>");
                }
            }

            result.AppendLine("</table>");


            result.AppendLine("</body>");
            result.AppendLine("</html>");

            return result;
        }

        private void SendEmail(
            string[] recipients,
            string subject,
            string body,
            string attachmentName,
            byte[] attachmentContent)
        {
            if (subject == null || body == null)
            {
                return;
            }

            var mail = new MailMessage
            {
                From = new MailAddress(settings.From),
                Subject = subject,
                Body = body,
                IsBodyHtml = true
            };

            AddRecipients(recipients, mail);

            BuildContent(attachmentName, attachmentContent, mail);

            smtpClient.Send(mail);
        }

        public struct GroupedRules
        {
            public IEnumerable<GroupedRulesMetadata> RulesAggregation { get; set; }
            public Severity Severity { get; set; }
        }

        public struct GroupedRulesMetadata
        {
            public int BrokenRulesCount { get; set; }

            public BusinessResearchProduct ResearchProduct { get; set; }

            public string RuleName { get; set; }
        }
    }
}
